{
  "cells": [
    {
      "metadata": {
        "id": "-n3Rngfddxii"
      },
      "cell_type": "markdown",
      "source": [
        "This notebook is a basic tutorial that demonstrates how to configure a simulation using Concordia."
      ]
    },
    {
      "metadata": {
        "id": "T6qSDTZtdeXk"
      },
      "cell_type": "markdown",
      "source": [
        "<a href=\"https://colab.research.google.com/github/google-deepmind/concordia/blob/main/examples/selling_cookies.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "metadata": {
        "id": "o4NhU-Z6cbop"
      },
      "cell_type": "code",
      "source": [
        "# @title Colab-specific setup (use a CodeSpace to avoid the need for this).\n",
        "try:\n",
        "  %env COLAB_RELEASE_TAG\n",
        "except:\n",
        "  pass  # Not running in colab.\n",
        "else:\n",
        "  %pip install --ignore-requires-python --requirement 'https://raw.githubusercontent.com/google-deepmind/concordia/main/examples/requirements.in' 'git+https://github.com/google-deepmind/concordia.git#egg=gdm-concordia'\n",
        "  %pip list"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "R8N4hGX5ci_d"
      },
      "cell_type": "code",
      "source": [
        "# @title Imports\n",
        "\n",
        "from collections.abc import Mapping, Sequence\n",
        "from concordia.contrib import language_models as language_model_utils\n",
        "import concordia.prefabs.entity as entity_prefabs\n",
        "import concordia.prefabs.game_master as game_master_prefabs\n",
        "from concordia.prefabs.simulation import generic as simulation\n",
        "from concordia.typing import entity as entity_lib\n",
        "from concordia.typing import prefab as prefab_lib\n",
        "from concordia.typing import scene as scene_lib\n",
        "from concordia.utils import helper_functions\n",
        "from IPython import display\n",
        "import numpy as np\n",
        "import sentence_transformers"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "a9Pb-LdTcOeZ"
      },
      "cell_type": "code",
      "source": [
        "# @title Language Model Selection: provide key or select DISABLE_LANGUAGE_MODEL\n",
        "\n",
        "# By default this colab uses models via an external API so you must provide an\n",
        "# API key. TogetherAI offers open weights models from all sources.\n",
        "\n",
        "API_KEY = ''  # @param {type: 'string'}\n",
        "# See concordia/language_model/utils.py\n",
        "API_TYPE = 'openai'  # e.g. 'together_ai' or 'openai'.\n",
        "MODEL_NAME = (  # for API_TYPE = 'together_ai', we recommend MODEL_NAME = 'google/gemma-3-27b-it'\n",
        "    'gpt-5'\n",
        ")\n",
        "# To debug without spending money on API calls, set DISABLE_LANGUAGE_MODEL=True\n",
        "DISABLE_LANGUAGE_MODEL = False"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "Za2zOpeTci8_"
      },
      "cell_type": "code",
      "source": [
        "# @title Use the selected language model\n",
        "\n",
        "# Note that it is also possible to use local models or other API models,\n",
        "# simply replace this cell with the correct initialization for the model\n",
        "# you want to use.\n",
        "\n",
        "if not DISABLE_LANGUAGE_MODEL and not API_KEY:\n",
        "  raise ValueError('API_KEY is required.')\n",
        "\n",
        "model = language_model_utils.language_model_setup(\n",
        "    api_type=API_TYPE,\n",
        "    model_name=MODEL_NAME,\n",
        "    api_key=API_KEY,\n",
        "    disable_language_model=DISABLE_LANGUAGE_MODEL,\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "KC0dbTwLci6k"
      },
      "cell_type": "code",
      "source": [
        "# @title Setup sentence encoder\n",
        "\n",
        "if DISABLE_LANGUAGE_MODEL:\n",
        "  embedder = lambda _: np.ones(3)\n",
        "else:\n",
        "  st_model = sentence_transformers.SentenceTransformer(\n",
        "      'sentence-transformers/all-mpnet-base-v2'\n",
        "  )\n",
        "  embedder = lambda x: st_model.encode(x, show_progress_bar=False)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "yN8NEgajci3_"
      },
      "cell_type": "code",
      "source": [
        "test = model.sample_text(\n",
        "    'Is societal and technological progress like getting a clearer picture of '\n",
        "    'something true and deep?'\n",
        ")\n",
        "print(test)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "Ya7BvCG1cwNY"
      },
      "cell_type": "code",
      "source": [
        "# @title Load prefabs from packages to make the specific palette to use here.\n",
        "\n",
        "prefabs = {\n",
        "    **helper_functions.get_package_classes(entity_prefabs),\n",
        "    **helper_functions.get_package_classes(game_master_prefabs),\n",
        "}"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "TSB7w33mRMOj"
      },
      "cell_type": "code",
      "source": [
        "# @title Print menu of prefabs\n",
        "\n",
        "display.display(\n",
        "    display.Markdown(helper_functions.print_pretty_prefabs(prefabs))\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "cKefuoQzj7tA"
      },
      "cell_type": "code",
      "source": [
        "\"\"\"A prefab implementing an entity with a minimal set of components.\"\"\"\n",
        "\n",
        "from collections.abc import Mapping\n",
        "import dataclasses\n",
        "\n",
        "from concordia.agents import entity_agent_with_logging\n",
        "from concordia.associative_memory import basic_associative_memory\n",
        "from concordia.components import agent as agent_components\n",
        "from concordia.language_model import language_model\n",
        "from concordia.typing import prefab as prefab_lib\n",
        "\n",
        "DEFAULT_INSTRUCTIONS_COMPONENT_KEY = 'Instructions'\n",
        "DEFAULT_INSTRUCTIONS_PRE_ACT_LABEL = '\\nInstructions'\n",
        "DEFAULT_GOAL_COMPONENT_KEY = 'Goal'\n",
        "\n",
        "\n",
        "@dataclasses.dataclass\n",
        "class MyAgent(prefab_lib.Prefab):\n",
        "  \"\"\"A prefab implementing an entity with a minimal set of components.\"\"\"\n",
        "\n",
        "  description: str = (\n",
        "      'An entity that has a minimal set of components and is configurable by'\n",
        "      ' the user. The initial set of components manage memory, observations,'\n",
        "      ' and instructions. If goal is specified, the entity will have a goal '\n",
        "      'constant component.'\n",
        "  )\n",
        "  params: Mapping[str, str] = dataclasses.field(\n",
        "      default_factory=lambda: {\n",
        "          'name': 'Alice',\n",
        "          'goal': '',\n",
        "      }\n",
        "  )\n",
        "\n",
        "  def build(\n",
        "      self,\n",
        "      model: language_model.LanguageModel,\n",
        "      memory_bank: basic_associative_memory.AssociativeMemoryBank,\n",
        "  ) -> entity_agent_with_logging.EntityAgentWithLogging:\n",
        "    \"\"\"Build an agent.\n",
        "\n",
        "    Args:\n",
        "      model: The language model to use.\n",
        "      memory_bank: The agent's memory_bank object.\n",
        "\n",
        "    Returns:\n",
        "      An entity.\n",
        "    \"\"\"\n",
        "\n",
        "    agent_name = self.params.get('name', 'Alice')\n",
        "\n",
        "    instructions = agent_components.instructions.Instructions(\n",
        "        agent_name=agent_name,\n",
        "        pre_act_label=DEFAULT_INSTRUCTIONS_PRE_ACT_LABEL,\n",
        "    )\n",
        "\n",
        "    observation_to_memory = agent_components.observation.ObservationToMemory()\n",
        "\n",
        "    observation_label = '\\nObservation'\n",
        "    observation = agent_components.observation.LastNObservations(\n",
        "        history_length=100, pre_act_label=observation_label\n",
        "    )\n",
        "\n",
        "    principle = agent_components.question_of_recent_memories.QuestionOfRecentMemories(\n",
        "        model=model,\n",
        "        pre_act_label=f'{agent_name} main guiding principle:',\n",
        "        question=(\n",
        "            f'How can {agent_name} exploit the situation for personal '\n",
        "            'gain and gratification?'\n",
        "        ),\n",
        "        answer_prefix=f'{agent_name} understands that ',\n",
        "        add_to_memory=False,\n",
        "    )\n",
        "\n",
        "    components_of_agent = {\n",
        "        DEFAULT_INSTRUCTIONS_COMPONENT_KEY: instructions,\n",
        "        'observation_to_memory': observation_to_memory,\n",
        "        agent_components.observation.DEFAULT_OBSERVATION_COMPONENT_KEY: (\n",
        "            observation\n",
        "        ),\n",
        "        agent_components.memory.DEFAULT_MEMORY_COMPONENT_KEY: (\n",
        "            agent_components.memory.AssociativeMemory(memory_bank=memory_bank)\n",
        "        ),\n",
        "        'principle': principle,\n",
        "    }\n",
        "\n",
        "    component_order = list(components_of_agent.keys())\n",
        "\n",
        "    if self.params.get('goal', ''):\n",
        "      goal_key = DEFAULT_GOAL_COMPONENT_KEY\n",
        "      goal = agent_components.constant.Constant(\n",
        "          state=self.params.get('goal', ''),\n",
        "          pre_act_label='Overarching goal',\n",
        "      )\n",
        "      components_of_agent[goal_key] = goal\n",
        "      # Place goal after the instructions.\n",
        "      component_order.insert(1, goal_key)\n",
        "\n",
        "    act_component = agent_components.concat_act_component.ConcatActComponent(\n",
        "        model=model,\n",
        "        component_order=component_order,\n",
        "    )\n",
        "\n",
        "    agent = entity_agent_with_logging.EntityAgentWithLogging(\n",
        "        agent_name=agent_name,\n",
        "        act_component=act_component,\n",
        "        context_components=components_of_agent,\n",
        "    )\n",
        "\n",
        "    return agent"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "fEOZ0lhBtfaB"
      },
      "cell_type": "code",
      "source": [
        "prefabs['myagent__Entity'] = MyAgent()"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "lVmcMrkj12A0"
      },
      "cell_type": "code",
      "source": [
        "DEFAULT_NAME = 'decision rules'\n",
        "\n",
        "PLAYER_ONE = 'Alice'\n",
        "PLAYER_TWO = 'Bob'\n",
        "\n",
        "\n",
        "def configure_scenes() -> Sequence[scene_lib.SceneSpec]:\n",
        "  \"\"\"Configure default scenes for this simulation.\"\"\"\n",
        "  decision = scene_lib.SceneTypeSpec(\n",
        "      name='decision',\n",
        "      game_master_name=DEFAULT_NAME,\n",
        "      action_spec={\n",
        "          PLAYER_ONE: entity_lib.choice_action_spec(\n",
        "              call_to_action='Would {name} buy the cookies from Bob?',\n",
        "              options=['Yes', 'No'],\n",
        "          ),\n",
        "      },\n",
        "  )\n",
        "\n",
        "  conversation = scene_lib.SceneTypeSpec(\n",
        "      name='conversation',\n",
        "      game_master_name='conversation rules',\n",
        "      action_spec=entity_lib.free_action_spec(\n",
        "          call_to_action=entity_lib.DEFAULT_CALL_TO_SPEECH\n",
        "      ),\n",
        "  )\n",
        "\n",
        "  scenes = [\n",
        "      scene_lib.SceneSpec(\n",
        "          scene_type=conversation,\n",
        "          participants=[PLAYER_ONE, PLAYER_TWO],\n",
        "          num_rounds=4,\n",
        "          premise={\n",
        "              PLAYER_ONE: [f'{PLAYER_ONE} is approached by {PLAYER_TWO}'],\n",
        "              PLAYER_TWO: [f'{PLAYER_TWO} has approached {PLAYER_ONE}'],\n",
        "          },\n",
        "      ),\n",
        "      scene_lib.SceneSpec(\n",
        "          scene_type=decision,\n",
        "          participants=[PLAYER_ONE],\n",
        "          num_rounds=1,\n",
        "          premise={\n",
        "              PLAYER_ONE: [\n",
        "                  f'{PLAYER_ONE} has to decide whether to buy cookies from'\n",
        "                  f' {PLAYER_TWO}'\n",
        "              ],\n",
        "          },\n",
        "      ),\n",
        "  ]\n",
        "  return scenes\n",
        "\n",
        "\n",
        "def action_to_scores(\n",
        "    joint_action: Mapping[str, str],\n",
        ") -> Mapping[str, float]:\n",
        "  \"\"\"Map a joint action to a dictionary of scores for each player.\"\"\"\n",
        "  if joint_action[PLAYER_ONE] == 'Yes':\n",
        "    return {PLAYER_ONE: -1, PLAYER_TWO: 1}\n",
        "  return {PLAYER_ONE: 1, PLAYER_TWO: -1}\n",
        "\n",
        "\n",
        "def scores_to_observation(scores: Mapping[str, float]) -> Mapping[str, str]:\n",
        "  \"\"\"Map a dictionary of scores for each player to a string observation.\n",
        "\n",
        "  This function is appropriate for a coordination game structure.\n",
        "\n",
        "  Args:\n",
        "    scores: A dictionary of scores for each player.\n",
        "\n",
        "  Returns:\n",
        "    A dictionary of observations for each player.\n",
        "  \"\"\"\n",
        "  observations = {}\n",
        "  for player_name in scores:\n",
        "    if scores[player_name] > 0:\n",
        "      observations[player_name] = f'{player_name} enjoyed the transaction.'\n",
        "    else:\n",
        "      observations[player_name] = (\n",
        "          f'{player_name} did not enjoy the transaction.'\n",
        "      )\n",
        "  return observations"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "JXz8A_s4s16w"
      },
      "cell_type": "code",
      "source": [
        "scenes = configure_scenes()"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "bREMbhkVcwQH"
      },
      "cell_type": "code",
      "source": [
        "# @title Configure instances.\n",
        "\n",
        "instances = [\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='basic__Entity',\n",
        "        role=prefab_lib.Role.ENTITY,\n",
        "        params={\n",
        "            'name': PLAYER_ONE,\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='myagent__Entity',\n",
        "        role=prefab_lib.Role.ENTITY,\n",
        "        params={\n",
        "            'name': PLAYER_TWO,\n",
        "            'goal': f'Sell cookies to {PLAYER_ONE}',\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='game_theoretic_and_dramaturgic__GameMaster',\n",
        "        role=prefab_lib.Role.GAME_MASTER,\n",
        "        params={\n",
        "            'name': 'decision rules',\n",
        "            # Comma-separated list of thought chain steps.\n",
        "            'scenes': scenes,\n",
        "            'action_to_scores': action_to_scores,\n",
        "            'scores_to_observation': scores_to_observation,\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='dialogic_and_dramaturgic__GameMaster',\n",
        "        role=prefab_lib.Role.GAME_MASTER,\n",
        "        params={\n",
        "            'name': 'conversation rules',\n",
        "            # Comma-separated list of thought chain steps.\n",
        "            'scenes': scenes,\n",
        "        },\n",
        "    ),\n",
        "    prefab_lib.InstanceConfig(\n",
        "        prefab='formative_memories_initializer__GameMaster',\n",
        "        role=prefab_lib.Role.INITIALIZER,\n",
        "        params={\n",
        "            'name': 'initial setup rules',\n",
        "            'next_game_master_name': 'conversation rules',\n",
        "            'shared_memories': [\n",
        "                (\n",
        "                    f'There is a small town of Riverbend where {PLAYER_ONE} and'\n",
        "                    f' {PLAYER_TWO} grew up.'\n",
        "                ),\n",
        "            ],\n",
        "            'player_specific_memories': {\n",
        "                PLAYER_ONE: [\n",
        "                    f'{PLAYER_ONE} will do anything for a charitable cause.',\n",
        "                    f'{PLAYER_ONE} does not like cookies',\n",
        "                ],\n",
        "                PLAYER_TWO: [f'{PLAYER_TWO} is a cookie salesman.'],\n",
        "            },\n",
        "            'player_specific_context': {\n",
        "                PLAYER_ONE: f'{PLAYER_ONE} does not like cookies.',\n",
        "                PLAYER_TWO: f'{PLAYER_TWO} is a cookie salesman.',\n",
        "            },\n",
        "        },\n",
        "    ),\n",
        "]"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "LvZQZ2qmcwSU"
      },
      "cell_type": "code",
      "source": [
        "config = prefab_lib.Config(\n",
        "    default_premise=(\n",
        "        'It is a bright sunny day in the town of Riverbend. The sun is in the'\n",
        "        f' zenith and the gentle breeze is rocking the trees. {PLAYER_ONE} is'\n",
        "        f' standing on the porch of their house. {PLAYER_TWO} has approached'\n",
        "        f' {PLAYER_ONE}'\n",
        "    ),\n",
        "    default_max_steps=5,\n",
        "    prefabs=prefabs,\n",
        "    instances=instances,\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "dJiP0nFzdHwM"
      },
      "cell_type": "markdown",
      "source": [
        "# The simulation"
      ]
    },
    {
      "metadata": {
        "id": "6-aNTmgacwbk"
      },
      "cell_type": "code",
      "source": [
        "# @title Initialize the simulation\n",
        "runnable_simulation = simulation.Simulation(\n",
        "    config=config,\n",
        "    model=model,\n",
        "    embedder=embedder,\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "dl1wJP9EcweG"
      },
      "cell_type": "code",
      "source": [
        "# @title Run the simulation\n",
        "raw_log = []\n",
        "results_log = runnable_simulation.play(max_steps=5, raw_log=raw_log)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "oQvo3alwdMgE"
      },
      "cell_type": "code",
      "source": [
        "# @title Display the log\n",
        "display.HTML(results_log)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {
        "id": "S8TdyvoYdSV8"
      },
      "cell_type": "markdown",
      "source": [
        "```\n",
        "Copyright 2024 DeepMind Technologies Limited.\n",
        "\n",
        "Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "you may not use this file except in compliance with the License.\n",
        "You may obtain a copy of the License at\n",
        "\n",
        "    https://www.apache.org/licenses/LICENSE-2.0\n",
        "\n",
        "Unless required by applicable law or agreed to in writing, software\n",
        "distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "See the License for the specific language governing permissions and\n",
        "limitations under the License.\n",
        "```"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "private_outputs": true,
      "provenance": [],
      "toc_visible": true
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
